﻿/// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Build.ComInteropWrapper;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.Win32;

namespace Microsoft.VisualStudio.Project
{
	/// <summary>
	/// Does security validation of a project before loading the project
	/// </summary>
	public class ProjectSecurityChecker : IDisposable
	{
		#region constants
		/// <summary>
		/// The dangereous target property.
		/// </summary>
		internal const string DangerousTargetProperty = "LoadTimeSensitiveTargets";

		/// <summary>
		/// The dangereous properties property.
		/// </summary>
		internal const string DangerousPropertyProperty = "LoadTimeSensitiveProperties";

		/// <summary>
		/// The dangereous items property.
		/// </summary>
		internal const string DangerousItemsProperty = "LoadTimeSensitiveItems";

		/// <summary>
		/// The check item locations property.
		/// </summary>
		internal const string CheckItemLocationProperty = "LoadTimeCheckItemLocation";

		/// <summary>
		/// The dangereous list item separator.
		/// </summary>
		internal const string DangerousListSeparator = ";";

		/// <summary>
		/// The project directory property.
		/// </summary>
		internal const string ProjectDirectoryProperty = "MSBuildProjectDirectory";

		/// <summary>
		/// The default dangereous properties.
		/// </summary>
		internal const string DefaultDangerousProperties = "LoadTimeSensitiveTargets;LoadTimeSensitiveProperties;LoadTimeSensitiveItems;LoadTimeCheckItemLocation;";

		/// <summary>
		/// The default dangereous targets.
		/// </summary>
		internal const string DefaultDangerousTargets = "Compile;GetFrameworkPaths;AllProjectOutputGroups;AllProjectOutputGroupsDependencies;CopyRunEnvironmentFiles;ResolveComReferences;ResolveAssemblyReferences;ResolveNativeReferences;";

		/// <summary>
		/// The default dangereous items.
		/// </summary>
		internal const string DefaultDangerousItems = ";";

		/// <summary>
		/// Defined the safe imports subkey in the registry.
		/// </summary>
		internal const string SafeImportsSubkey = @"MSBuild\SafeImports";
		#endregion

		#region fields
		/// <summary>
		/// Defines an object that will be a mutex for this object for synchronizing thread calls.
		/// </summary>
		private static volatile object Mutex = new object();

		/// <summary>
		/// Flag determining if the object has been disposed.
		/// </summary>
		private bool isDisposed;

		/// <summary>
		/// The associated project shim for the project file
		/// </summary>
		private ProjectShim projectShim;

		/// <summary>
		/// The security check helper object used to call out to do necessary security checkings.
		/// </summary>
		private SecurityCheckHelper securityCheckHelper = new SecurityCheckHelper();

		/// <summary>
		/// The associated service provider.
		/// </summary>
		private IServiceProvider serviceProvider;

		#endregion

		#region properties
		/// <summary>
		/// The associated project shim for the project file
		/// </summary>
		/// <devremark>The project shim is made internal in order to be able to be passed to the user project.</devremark>
		internal protected ProjectShim ProjectShim
		{
			get { return this.projectShim; }
		}

		/// <summary>
		/// The security check helper that will be used to perform the necessary checkings.
		/// </summary>
		protected SecurityCheckHelper SecurityCheckHelper
		{
			get { return this.securityCheckHelper; }
		}

		/// <summary>
		/// The associated service provider.
		/// </summary>
		protected IServiceProvider ServiceProvider
		{
			get
			{
				return this.serviceProvider;
			}
		}
		#endregion

		#region ctors
		/// <summary>
		/// Overloaded Constructor 
		/// </summary>
		/// <param name="projectFilePath">path to the project file</param>
		/// <param name="serviceProvider">A service provider.</param>
		public ProjectSecurityChecker(IServiceProvider serviceProvider, string projectFilePath)
		{
			if(serviceProvider == null)
			{
				throw new ArgumentNullException("serviceProvider");
			}

			if(String.IsNullOrEmpty(projectFilePath))
			{
				throw new ArgumentException(SR.GetString(SR.ParameterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "projectFilePath");
			}

			this.serviceProvider = serviceProvider;

			// Instantiate a new project shim that we are going to use for security checkings.
			EngineShim engine = new EngineShim();
			this.projectShim = engine.CreateNewProject();
			this.projectShim.Load(projectFilePath);
		}
		#endregion

		#region IDisposable Members

		/// <summary>
		/// The IDispose interface Dispose method for disposing the object determinastically.
		/// </summary>
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		#endregion

		#region virtual methods
		/// <summary>
		/// Check if the project is safe at load/design time
		/// </summary>
		/// <param name="securityErrorMessage">If the project is not safe contains an error message, describing the reason.</param>
		/// <returns>true if the project is safe, false otherwise</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
			Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
		public virtual bool IsProjectSafeAtLoadTime(out string securityErrorMessage)
		{
			securityErrorMessage = String.Empty;

			StringBuilder securityMessageMaker = new StringBuilder();
			int counter = 0;
			string tempMessage;

			// STEP 1: Check direct imports.
			if(!this.IsProjectSafeWithImports(out tempMessage))
			{
				ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
				securityErrorMessage = tempMessage;
			}

			// STEP 2: Check dangerous properties
			if(!this.IsProjectSafeWithProperties(out tempMessage))
			{
				ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
				securityErrorMessage = tempMessage;
			}

			// STEP 3: Check dangerous targets
			if(!this.IsProjectSafeWithTargets(out tempMessage))
			{
				ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
				securityErrorMessage = tempMessage;
			}

			// STEP 4: Check dangerous items
			if(!this.IsProjectSafeWithItems(out tempMessage))
			{
				ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
				securityErrorMessage = tempMessage;
			}

			// STEP 5: Check UsingTask tasks
			if(!this.IsProjectSafeWithUsingTasks(out tempMessage))
			{
				ProjectSecurityChecker.FormatMessage(securityMessageMaker, ++counter, tempMessage);
				securityErrorMessage = tempMessage;
			}

			// STEP 6: Check for items defined within the LoadTimeCheckItemLocation, whether they are defined in safe locations
			if(!this.CheckItemsLocation(out tempMessage))
			{
				securityMessageMaker.AppendFormat(CultureInfo.CurrentCulture, "{0}: ", (++counter).ToString(CultureInfo.CurrentCulture));
				securityMessageMaker.AppendLine(tempMessage);
				securityErrorMessage = tempMessage;
			}

			if(counter > 1)
			{
				securityErrorMessage = securityMessageMaker.ToString();
			}

			return String.IsNullOrEmpty(securityErrorMessage);
		}

		/// <summary>
		/// Checks if the project is safe with imports. The project file is considered
		/// unsafe if it contains any imports not registered in the safe import regkey.
		/// </summary>
		/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
		/// <returns>true if the project is safe regarding imports.</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
			Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
		protected virtual bool IsProjectSafeWithImports(out string securityErrorMessage)
		{
			securityErrorMessage = String.Empty;

			// Now get the directly imports and do the comparision.
			string[] directImports = this.securityCheckHelper.GetDirectlyImportedProjects(this.projectShim);
			if(directImports != null && directImports.Length > 0)
			{
				IList<string> safeImportList = ProjectSecurityChecker.GetSafeImportList();

				for(int i = 0; i < directImports.Length; i++)
				{
					string fileToCheck = directImports[i];
					if(!ProjectSecurityChecker.IsSafeImport(safeImportList, fileToCheck))
					{
						using(RegistryKey root = VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration))
						{
							securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsImport, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), fileToCheck, Path.Combine(root.Name, SafeImportsSubkey));
						}

						return false;
					}
				}
			}

			return true;
		}



		/// <summary>
		/// Checks if the project is safe regarding properties.
		/// </summary>
		/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>       
		/// <returns>true if the project has only safe properties.</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
			Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
		protected virtual bool IsProjectSafeWithProperties(out string securityErrorMessage)
		{
			securityErrorMessage = String.Empty;

			// Now ask the security check heper for the safe properties.
			string reasonForFailure;
			bool isUserFile;
			bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousPropertyProperty,
																		ProjectSecurityChecker.DefaultDangerousProperties,
																		this.projectShim,
																		null,
																		SecurityCheckPass.Properties,
																		out reasonForFailure,
																		out isUserFile);

			if(!isProjectSafe)
			{
				securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsProperty);
			}

			return isProjectSafe;
		}

		/// <summary>
		/// Checks if the project is safe regarding targets.
		/// </summary>
		/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>       
		/// <returns>true if the project has only safe targets.</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
			Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
		protected virtual bool IsProjectSafeWithTargets(out string securityErrorMessage)
		{
			securityErrorMessage = String.Empty;

			// Now ask the security check heper for the safe targets.
			string reasonForFailure;
			bool isUserFile;
			bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousTargetProperty,
																		ProjectSecurityChecker.DefaultDangerousTargets,
																		this.projectShim,
																		null,
																		SecurityCheckPass.Targets,
																		out reasonForFailure,
																		out isUserFile);

			if(!isProjectSafe)
			{
				securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsTarget);
			}

			return isProjectSafe;
		}

		/// <summary>
		/// Checks if the project is safe regarding items.
		/// </summary>
		/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>       
		/// <returns>true if the project has only safe items.</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
			Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
		protected virtual bool IsProjectSafeWithItems(out string securityErrorMessage)
		{
			securityErrorMessage = String.Empty;

			// Now ask the security check heper for the safe items.
			string reasonForFailure;
			bool isUserFile;
			bool isProjectSafe = this.securityCheckHelper.IsProjectSafe(ProjectSecurityChecker.DangerousItemsProperty,
																		ProjectSecurityChecker.DefaultDangerousItems,
																		this.projectShim,
																		null,
																		SecurityCheckPass.Items,
																		out reasonForFailure,
																		out isUserFile);

			if(!isProjectSafe)
			{
				securityErrorMessage = this.GetMessageString(reasonForFailure, SR.DetailsItem);
			}

			return isProjectSafe;
		}

		/// <summary>
		/// Checks if the project is safe with using tasks.
		/// </summary>
		/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
		/// <returns>true if the project has no using tasks defined in the project file.</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
			Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
		protected virtual bool IsProjectSafeWithUsingTasks(out string securityErrorMessage)
		{
			securityErrorMessage = String.Empty;

			string[] usingTasks = this.securityCheckHelper.GetNonImportedUsingTasks(this.projectShim);

			if(usingTasks != null && usingTasks.Length > 0)
			{
				securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsUsingTask, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), usingTasks[0]);
				return false;
			}

			return true;
		}

		/// <summary>
		///  If the project contains the LoadTimeCheckItemsWithinProjectCone property, the method verifies that all the items listed in there are within the project cone.
		///  Also checks that the project is not in Program Files or Windows if the property was there.
		/// </summary>
		/// <param name="securityErrorMessage">At return describes the reason why the projects is not considered safe.</param>
		/// <returns>true if the project has no badly defined project items.</returns>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#",
			Justification = "The error message needs to be an out parameter. We are following here the Try... method patterns.")]
		protected virtual bool CheckItemsLocation(out string securityErrorMessage)
		{
			securityErrorMessage = String.Empty;

			// Get the <LoadTimeCheckItemLocation> property from the project
			string itemLocationProperty = this.projectShim.GetEvaluatedProperty(ProjectSecurityChecker.CheckItemLocationProperty);

			if(String.IsNullOrEmpty(itemLocationProperty))
			{
				return true;
			}

			// Takes a semicolon separated list of entries, splits them and puts them into a list with values trimmed.
			string[] items = itemLocationProperty.Split(ProjectSecurityChecker.DangerousListSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

			IList<string> itemsToCheck = new List<string>();
			foreach(string item in items)
			{
				itemsToCheck.Add(item.Trim());
			}

			// Now check the items for being defined in a safe location.
			string reasonForFailure;
			ItemSecurityChecker itemsSecurityChecker = new ItemSecurityChecker(this.serviceProvider, this.projectShim.FullFileName);
			if(!itemsSecurityChecker.CheckItemsSecurity(this.projectShim, itemsToCheck, out reasonForFailure))
			{
				securityErrorMessage = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.DetailsItemLocation, CultureInfo.CurrentUICulture), Path.GetFileName(this.projectShim.FullFileName), reasonForFailure);
				return false;
			}

			return true;
		}


		/// <summary>
		/// The method that does the cleanup.
		/// </summary>
		/// <param name="disposing">true if called from IDispose.Dispose; false if called from Finalizer.</param>
		protected virtual void Dispose(bool disposing)
		{
			// Everybody can go here.
			if(!this.isDisposed)
			{
				// Synchronize calls to the Dispose simultaniously.
				lock(Mutex)
				{
					if(disposing)
					{
						this.projectShim.ParentEngine.UnloadProject(this.projectShim);
					}

					this.isDisposed = true;
				}
			}
		}
		#endregion

		#region helper methods
		/// <summary>
		/// Gets a message string that has an associated format with a reason for failure.
		/// </summary>
		/// <param name="reasonForFailure"></param>
		/// <param name="resourceID"></param>
		/// <returns></returns>
		internal string GetMessageString(string reasonForFailure, string resourceID)
		{
			Debug.Assert(!String.IsNullOrEmpty(reasonForFailure), "The reason for failure should not be empty or null");
			Debug.Assert(!String.IsNullOrEmpty(resourceID), "The resource id string cannot be empty");

			return String.Format(CultureInfo.CurrentCulture, SR.GetString(resourceID, Path.GetFileName(this.projectShim.FullFileName), reasonForFailure));
		}

		/// <summary>
		/// Generates a format string that will be pushed to the More Detailed dialog.
		/// </summary>
		/// <param name="securityMessageMaker">The Stringbuilder object containing the formatted message.</param>
		/// <param name="counter">The 'issue' number.</param>
		/// <param name="securityErrorMessage">The message to format.</param>
		private static void FormatMessage(StringBuilder securityMessageMaker, int counter, string securityErrorMessage)
		{
			securityMessageMaker.AppendFormat(CultureInfo.CurrentCulture, "{0}: ", counter.ToString(CultureInfo.CurrentCulture));
			securityMessageMaker.AppendLine(securityErrorMessage);
			securityMessageMaker.Append(Environment.NewLine);
		}

		/// <summary>
		/// Returns a set of file info's describing the files in the SafeImports registry location.
		/// </summary>
		/// <returns>A set of FileInfo objects describing the files in the SafeImports location.</returns>
		private static IList<string> GetSafeImportList()
		{
			List<string> importsList = new List<string>();

			using(RegistryKey root = VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration))
			{
				if(root != null)
				{
					using(RegistryKey key = root.OpenSubKey(SafeImportsSubkey))
					{
						if(key != null)
						{
							foreach(string value in key.GetValueNames())
							{
								string keyValue = key.GetValue(value, String.Empty, RegistryValueOptions.None) as string;
								// Make sure that the environment variables are expanded.
								keyValue = System.Environment.ExpandEnvironmentVariables(keyValue);
								Uri uri;
								if(!String.IsNullOrEmpty(keyValue) && Uri.TryCreate(keyValue, UriKind.Absolute, out uri) && uri.IsAbsoluteUri)
								{
									importsList.Add(keyValue);
								}
							}
						}
					}
				}
			}

			return importsList;
		}

		/// <summary>
		/// Checks if an import is a safe import.
		/// </summary>
		/// <param name="safeImportsList">A list of safe imports from teh registry.</param>
		/// <param name="fileToCheck">The file to check.</param>
		/// <returns>true if the file to check can be found in the safe import list</returns>
		private static bool IsSafeImport(IList<string> safeImportsList, string fileToCheck)
		{
			foreach(string safeImport in safeImportsList)
			{
				if(NativeMethods.IsSamePath(safeImport, fileToCheck))
				{
					return true;
				}
			}

			return false;
		}
		#endregion

		#region nested types
		/// <summary>
		/// Class for checking that the items defined in LoadTimeCheckItemLocation are being defined in safe locations.
		/// </summary>
		private class ItemSecurityChecker
		{
			#region fields
			/// <summary>
			/// The associated service provider.
			/// </summary>
			private IServiceProvider serviceProvider;

			/// <summary>
			/// The solutionFolder;
			/// </summary>
			private Uri solutionFolder;

			/// <summary>
			/// The project folder
			/// </summary>
			private Uri projectFolder;

			/// <summary>
			/// The set of special folders.
			/// </summary>
			private IList<Uri> specialFolders;
			#endregion

			#region ctors
			/// <summary>
			/// Overloaded Constructor 
			/// </summary>
			/// <param name="projectFilePath">path to the project file</param>
			/// <param name="serviceProvider">A service provider.</param>
			internal ItemSecurityChecker(IServiceProvider serviceProvider, string projectFullPath)
			{
				this.serviceProvider = serviceProvider;

				// Initialize the project and solution folders.
				this.SetProjectFolder(projectFullPath);
				this.SetSolutionFolder();

				// Set the special folders. Maybe this should be a static.
				this.specialFolders = ItemSecurityChecker.SetSpecialFolders();
			}
			#endregion

			#region methods
			/// <summary>
			/// Checks whether a set of project items described by the LoadTimeCheckItemLocation are in a safe location.
			/// </summary>
			/// <param name="projectShim">The project shim containing the items to be checked.</param>
			/// <param name="itemsToCheck">The list of items to check if they are in the project cone.</param>
			/// <param name="reasonForFailure">The reason for failure if any of the files fails</param>
			/// <returns>true if all project items are in the project cone. Otherwise false.</returns>
			internal bool CheckItemsSecurity(ProjectShim projectShim, IList<string> itemsToCheck, out string reasonForFailure)
			{
				reasonForFailure = String.Empty;

				// If nothing to check assume that everything is ok.
				if(itemsToCheck == null)
				{
					return true;
				}

				Debug.Assert(projectShim != null, "Cannot check the items if no project has been defined!");

				foreach(string itemName in itemsToCheck)
				{
					BuildItemGroupShim group = projectShim.GetEvaluatedItemsByNameIgnoringCondition(itemName);
					if(group != null)
					{
						IEnumerator enumerator = group.GetEnumerator();
						while(enumerator.MoveNext())
						{
							BuildItemShim item = enumerator.Current as BuildItemShim;

							string finalItem = item.FinalItemSpec;

							if(!String.IsNullOrEmpty(finalItem))
							{
								// Perform the actual check - start with normalizing the path.  Relative paths
								// should be treated as relative to the project file.
								string fullPath = this.GetFullPath(finalItem);

								// If the fullpath of the item is suspiciously short do not check it.
								if(fullPath.Length >= 3)
								{
									Uri uri = null;

									// If we cannot create a uri from the item path return with the error
									if(!Uri.TryCreate(fullPath, UriKind.Absolute, out uri))
									{
										reasonForFailure = fullPath;
										return false;
									}

									// Check if the item points to a network share
									if(uri.IsUnc)
									{
										reasonForFailure = fullPath;
										return false;
									}

									// Check if the item is located in a drive root directory
									if(uri.Segments.Length == 3 && uri.Segments[1] == ":" && uri.Segments[2][0] == Path.DirectorySeparatorChar)
									{
										reasonForFailure = fullPath;
										return false;
									}

									//Check if the item is not in a special folder.
									foreach(Uri specialFolder in this.specialFolders)
									{
										if(ItemSecurityChecker.IsItemInCone(uri, specialFolder))
										{
											reasonForFailure = fullPath;
											return false;
										}
									}
								}
								else
								{
									reasonForFailure = fullPath;
									return false;
								}
							}
						}
					}
				}

				return true;
			}


			/// <summary>
			/// Gets the list of special directories. This method should be optimized if called more then once.
			/// </summary>
			/// <returns>The list of special directories</returns>
			private static IList<Uri> SetSpecialFolders()
			{
				string[] specialFolderArray = new string[5]
                {
                   Environment.GetFolderPath(Environment.SpecialFolder.System),
                   Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
                   Environment.GetFolderPath(Environment.SpecialFolder.Startup),
                   ItemSecurityChecker.GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder.Windows),
                   ItemSecurityChecker.GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder.CommonStartup)
                };

				List<Uri> specialFolders = new List<Uri>(5);

				// Add trailing backslash to the folders.
				foreach(string specialFolder in specialFolderArray)
				{
					string tempFolder = specialFolder;
					if(!tempFolder.EndsWith("\\", StringComparison.Ordinal))
					{
						tempFolder += "\\";
					}

					specialFolders.Add(new Uri(tempFolder));
				}

				return specialFolders;
			}

			/// <summary>
			/// Some special folders are not supported by System.Environment.GetFolderPath. Get these special folders using p/invoke.
			/// </summary>
			/// <param name="specialFolder">The type of special folder to retrieve.</param>
			/// <returns>The folder path</returns>
			private static string GetSpecialDirectoryFromNative(NativeMethods.ExtendedSpecialFolder extendedSpecialFolder)
			{
				string specialFolder = null;
				IntPtr buffer = IntPtr.Zero;

				// Demand Unmanaged code permission. It should be normal to demand UnmanagedCodePermission from an assembly integrating into VS.
				new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
				try
				{
					buffer = Marshal.AllocHGlobal((NativeMethods.MAX_PATH + 1) * 2);
					IntPtr[] pathIdentifier = new IntPtr[1];

					if(ErrorHandler.Succeeded(UnsafeNativeMethods.SHGetSpecialFolderLocation(IntPtr.Zero, (int)extendedSpecialFolder, pathIdentifier)) && UnsafeNativeMethods.SHGetPathFromIDList(pathIdentifier[0], buffer))
					{
						specialFolder = Marshal.PtrToStringAuto(buffer);
					}
				}
				finally
				{
					if(buffer != IntPtr.Zero)
					{
						Marshal.FreeHGlobal(buffer);
					}
				}


				return specialFolder;
			}


			/// <summary>
			/// Checks if the itemToCheck is in the cone of the baseUri.
			/// </summary>
			/// <param name="itemToCheck">The item to check</param>
			/// <param name="baseUri">The base to the item. This should define a folder.</param>
			/// <returns>true if the item to check is in the cone of the baseUri.</returns>
			private static bool IsItemInCone(Uri itemToCheck, Uri baseUri)
			{
				Debug.Assert(itemToCheck != null && baseUri != null, "Cannot check for items since the input is wrong");
				Debug.Assert(!NativeMethods.IsSamePath(Path.GetDirectoryName(baseUri.LocalPath), baseUri.LocalPath), "The " + baseUri.LocalPath + " is not a folder!");

				return (itemToCheck.IsFile && baseUri.IsFile &&
					String.Compare(itemToCheck.LocalPath, 0, baseUri.LocalPath, 0, baseUri.LocalPath.Length, StringComparison.OrdinalIgnoreCase) == 0);
			}

			/// <summary>
			/// Sets the solution folder.
			/// </summary>
			private void SetSolutionFolder()
			{
				if(this.solutionFolder != null)
				{
					return;
				}

				IVsSolution solution = this.serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution;
				Debug.Assert(solution != null, "Could not retrieve the solution service from the global service provider");

				string solutionDirectory, solutionFile, userOptionsFile;

				// We do not want to throw. If we cannot set the solution related constants we set them to empty string.
				ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out solutionDirectory, out solutionFile, out userOptionsFile));

				if(String.IsNullOrEmpty(solutionDirectory))
				{
					return;
				}

				// Make sure the solution dir ends with a backslash
				if(solutionDirectory[solutionDirectory.Length - 1] != Path.DirectorySeparatorChar)
				{
					solutionDirectory += Path.DirectorySeparatorChar;
				}

				Uri.TryCreate(solutionDirectory, UriKind.Absolute, out this.solutionFolder);

				Debug.Assert(this.solutionFolder != null, "Could not create the Uri for the solution folder");
			}

			/// <summary>
			/// Sets the project folder.
			/// </summary>
			/// <param name="projectFullPath">The path to the project</param>
			private void SetProjectFolder(string projectFullPath)
			{
				if(this.projectFolder != null)
				{
					return;
				}

				string tempProjectFolder = Path.GetDirectoryName(projectFullPath);

				// Make sure the project dir ends with a backslash
				if(!tempProjectFolder.EndsWith("\\", StringComparison.Ordinal) && !tempProjectFolder.EndsWith("/", StringComparison.Ordinal))
				{
					tempProjectFolder += "\\";
				}

				Uri.TryCreate(tempProjectFolder, UriKind.Absolute, out this.projectFolder);

				Debug.Assert(this.projectFolder != null, "Could not create the Uri for the project folder");
			}

			/// <summary>
			/// Gets the fullpath of an item. 
			/// Relative pathes are treated as relative to the project file.
			/// </summary>
			/// <param name="item">The item.</param>
			/// <returns>The ful path of the item.</returns>
			private string GetFullPath(string item)
			{
				Url url;
				if(Path.IsPathRooted(item))
				{
					// Use absolute path
					url = new Microsoft.VisualStudio.Shell.Url(item);
				}
				else
				{
					// Path is relative, so make it relative to project path
					url = new Url(new Url(this.projectFolder.LocalPath), item);
				}

				return url.AbsoluteUrl;
			}
			#endregion
		}
		#endregion
	}
}
